home *** CD-ROM | disk | FTP | other *** search
- 4.1 Assembler: Grundlagen und Syntax 4 - 1
- ________________________________________________________
-
-
- 4. Der 68000 Assembler
-
- Die Megamax Modula-Implementation erlaubt Ihnen, innerhalb von Modula-Anwei~
- sungen Programmteile in 68000-Assembler einzufügen. Dazu ist im Compiler
- ein kompletter symbolischer Ein-Pass-Assembler integriert. Auf alle Datenstruk~
- turen und Prozeduren, die in Modula deklariert wurden, kann über die jeweiligen
- Bezeichner zugegriffen werden.
-
- Die folgende Beschreibung des Assemblers setzt voraus, daß Sie die 68000
- CPU in Assembler programmieren können. Dargestellt wird hier vor allem das
- Zusammenspiel von Assembler- und Modula-Programmteilen. Dabei werden Sie
- auch einiges über das 'Innenleben' des Modula-Compilers erfahren. Wenn Sie
- die Assemblerprogrammierung erst lernen wollen, sollten Sie eines der zahlreichen
- Lehrbücher konsultieren, z. B. Kane/Hawkins/Leventhal: 68000 Assembly
- Language Programming, Osborne/McGraw-Hill 1981
-
-
- 4.1 Grundlagen und Syntax
-
- Egal, ob Sie komplette Assembler-Prozeduren mit allen Raffinessen program~
- mieren wollen, oder nur mal einen Systemaufruf in drei Zeilen brauchen -
- diesen Abschnitt sollten Sie in jedem Fall lesen. (Danach trennen sich dann die
- Wege: Gelegenheits-Assemblerprogrammierer bitte in Abschnitt 4.2, Experten
- in 4.3 weiterlesen!)
-
-
- Assembler-Anweisungen in Modula-Programmen
-
- Der Megamax-Compiler erweitert die Modula-Syntax um die zusätzliche
- Anweisung ASSEMBLER, die aus dem SYSTEM-Modul importiert werden muß.
- Die Syntax lautet
-
- Statement = .. | ASSEMBLER AsmStatement END | ..
-
- Überall, wo eine Modula-Anweisung erwartet wird, kann also ein Block von
- Assembler-Anweisungen eingeschoben werden. Wie ein AsmStatement genau
- aussieht, erfahren Sie im folgenden Abschnitt. Vieles sehen Sie sicher schon
- dem folgenden Beispiel an, das die bitweise Spiegelung einer CARDINAL-Variablen
- ('Bit Reversal') demonstriert:
- 4.1 Assembler: Grundlagen und Syntax 4 - 2
- ________________________________________________________
-
-
- FOR k := 0 TO kMax DO
- :
- ASSEMBLER ; *** kRev = BitReversal (k)
- MOVE k, D0 ; hole Schleifenindex
- MOVEQ #15, D1 ; 16 Bit sind umzukehren
- lp LSR #1, D0 ; schiebe Bit ins eXtend-Flag
- ROXL #1, D2 ; .. und von dort ins Zielregister
- DBF D1, lp ; das Ganze 16 mal
- MOVE D2, kRev ; schreibe Ergebnis in die Variable kRev
- END;
- ...
- END (* FOR k *)
-
-
- Aufbau von Assembleranweisungen
-
- Der Aufbau einer Assembleranweisung ist Ihnen vermutlich von einem der
- konventionellen separaten Assembler bekannt:
-
- AsmStatement = Label Opcode Operand ';' Comment .
-
- Zu den einzelnen Komponenten des AsmStatement finden Sie in den folgenden
- Abschnitten dieses Kapitels nähere Informationen.
-
- Zu beachten ist, daß die Assembler-Instruktionen und die Registernamen
- immer in Großbuchstaben geschrieben sein müssen! Es ist nicht möglich, nur
- die Assembler-Blöcke mit der $C-Option zu klammern, um sie klein schreiben
- zu können. Aufgrund interner Gegebenheiten im Compiler würden Sie dann
- Schwierigkeiten haben, auf Variablen und Funktionen zuzugreifen. Statt dessen
- müßten Sie dann schon das ganze Modul mit $C- übersetzen.
-
- Übliche Assembler erwarten, daß Sie ein AsmStatement pro Textzeile in Ihren
- Quelltext schreiben. Der integrierte Megamax-Assembler paßt sich hier den
- Modula-Gepflogenheiten an: Er ist formatfrei; Sie können also ein AsmStatement
- über mehrere Zeilen verteilen oder auch mehrere Statements auf einer Zeile
- zusammenfassen. Eine Besonderheit ist allerdings zu beachten: Ein ';' leitet
- einen Kommentar ein, der stets bis zum Zeilenende reicht.
-
- Außer der Kommentierung nach ';' ist auch in Assemblerteilen die Verwendung
- der Modula-Kommentarklammern (* *) möglich. Sie erlaubt einfaches Ausklam~
- mern ganzer Programmteile.
- 4.1 Assembler: Grundlagen und Syntax 4 - 3
- ________________________________________________________
-
-
- Befehlscodes und Adressierungsarten
-
- Der Assembler übersetzt alle Befehlscodes und Adressierungsarten der 68000
- CPU. Zur Notation wird die Standard-Syntax von Motorola verwendet, wie sie
- in allen uns bekannten 68000-Lehrbüchern verwendet wird. Als Assembler-
- Könner dürfen Sie also ohne Einschränkungen drauflosprogrammieren.
-
- Einige Besonderheiten sind allerdings zu beachten: Teilweise muß die genaue
- Anweisung geschrieben werden, auch wenn der Assembler dies eigentlich
- selbst korrigieren könnte. Bei Nichtbeachtung meldet der Compiler einen Fehler.
- Beispiele:
-
- CMPI #x,D0 ; hier kann nicht CMP verwendet werden
- CMPA.L A0,A1 ; dito
- MOVE.L A2,A0 ; hier allerdings braucht nicht MOVEA stehen
-
- TST 0(A0,D0.W) ; Offset (hier: 0) und Größe (.W) sind notwendig
-
- CLR (A2) ; erzeugt indirekte Adr. ohne Offset
- CLR 0(A2) ; erzeugt indir. Adr. mit Offset
- CLR offs(A2) ; ist offs Null, wird indir. Adr. ohne Offset erzeugt
-
- Als Operanden können Sie unter anderem Konstanten, Variablen und Prozeduren
- verwenden, die Sie in Modula definiert haben. Allerdings müssen Sie etwas
- Rücksicht auf den Compiler und die Art, wie er seine Variablen und Prozeduren
- handhabt, nehmen. In den folgenden Abschnitten wird genau verraten, wie das
- funktioniert; hier schon einmal die wichtige Grundregel:
-
- Auf globale Objekte (Variable und Prozeduren) wird mit absoluter Adressierung
- zugegriffen; auf lokale Objekte und Label mit relativer Adressierung (Label und
- Prozeduren: PC-relativ; Variable: Adreßregister mit Displacement). Trotz der
- Verwendung absoluter Adressen bleiben auch Module mit Assemblerteilen stets
- verschiebbar: Der Assembler legt automatisch die Informationen an, die zum
- Umrechnen der Adressen vor dem Starten des fertigen Moduls benötigt werden.
-
- Bei Konstant-Ausdrücken verhält es sich exakt wie in CONST-Anweisungen.
- Auch stehen alle Modula-Funktionen, wie SIZE oder CHR, zur Verfügung.
- Lediglich, wenn negative Werte angegeben werden sollen, kann unter
- Umständen der Ausdruck nicht mit dem Minuszeichen beginnen. Statt dessen
- muß dann eine Null vorangestellt werden, um daraus eine Subtraktion zu
- machen. Beispiele:
-
- MULU #TSIZE (ArrayElement),D0
- MOVE.B #0-LENGTH (StringConst),D1
- 4.1 Assembler: Grundlagen und Syntax 4 - 4
- ________________________________________________________
-
-
- Label (Marken)
-
- Als Label (Sprungmarke) können alle Bezeichner verwendet werden, die in
- Modula als Variablennamen etc. zulässig wären. Reserviert sind allerdings alle
- Opcode-Bezeichnungen der 68000 und auch die Modula-Schlüsselworte. (Mein
- Lieblingsfehler: LOOP ist kein erlaubtes Label!) Natürlich dürfen Sie auch
- Bezeichner benutzen, die außerhalb des Assemblerteils bereits anderweitig
- (z. B. als globale Variable) definiert sind - auch der Assembler unterstützt das
- Modula-Konzept der Lokalität. Dabei müssen Sie allerdings Rücksicht darauf
- nehmen, daß auch der Assembler nur einen Durchgang durch den Quelltext
- macht: In diesen Fällen muß das Label definiert sein, bevor Sie das erste Mal
- darauf Bezug nehmen; sonst nimmt der Assembler an, die bereits bekannte
- Bedeutung des Bezeichners sei gemeint. Normalerweise darf ein Label aber
- ohne weiteres vor seiner Definition benutzt werden; die Definition muß dann im
- gleichen Prozedur- bzw. Modulrumpf nachfolgen.
-
- Labels darf ein Doppelpunkt hinten angestellt werden.
-
- Labels dürfen - wie schon erwähnt - nur in PC-relativen Adressierungsarten
- benutzt werden. Beispiele für korrekte Anwendungen:
-
- LEA Daten(PC), A0 ; Zugriff auf Tabellen
- LEA Ziel(PC), A1 ; gleich noch einer
- Schleife: MOVE (A0)+, (A1)+ ; diese Marke endet mit ':'
- BNE Schleife ; Bcc und BSR sind möglich
- RTS
- Daten DC.W 5, 4, 3, 2, 1, 0 ; der ':' muß aber nicht sein
- Ziel DS 20
-
-
- Relative Sprünge (Bcc und BSR)
-
- Die 68000 kennt bekanntlich relative Sprünge mit byte- oder wortlanger
- Angabe der Sprungweite. Wird dabei eine bereits bekannte Marke
- angesprungen, so verwendet der Assembler automatisch die passende
- Argumentgröße.
-
- Ist dagegen die angesprungene Marke noch undefiniert, so versucht der
- Assembler im Normalfall, einen kurzen Sprung zu erzeugen. Stellt sich bei
- Definition der Marke heraus, daß sie zu weit von einem solchen Sprung
- entfernt ist, so ist eine Änderung der Sprunglänge nicht mehr möglich - ein
- Assemblerfehler wird angezeigt.
-
- Um explizit die Erzeugung einer langen Sprungweite zu fordern, können Sie
- Bcc.W schreiben. Die explizite Forderung eines kurzen Sprungs ist nicht
- erforderlich, aber durch Bcc.S oder Bcc.B möglich.
- 4.2 Einfache Assembleranwendungen 4 - 5
- ________________________________________________________
-
-
- 4.2 Einfache Assembleranwendungen
-
- Wenn Sie gelegentlich ein paar maschinennahe Operationen in Assembler
- codieren möchten, ohne gleich das halbe Innenleben Ihres Atari zu studieren,
- sind Sie in diesem Absatz richtig. Die folgenden Informationen genügen zur
- Realisierung vieler Anwendungen. Die Darstellung aller Möglichkeiten des Assem~
- blers bleibt jedoch dem Abschnitt 4.3 vorbehalten.
-
-
- Belegung der CPU-Register
-
- Die Register der 68000 CPU dürfen Sie in Assemblerteilen nicht alle nach
- Belieben verwenden. Der vom Compiler erzeugte Code benutzt die Register als
- Zwischenspeicher. Einige Adreßregister haben besondere Funktionen und müssen
- daher sogar langfristig ihren Inhalt behalten.
-
- D0
- .. Zwischenspeicher
- D2
-
- D3
- .. reserviert
- D7
-
- A0 Zwischenspeicher
- A1 Zwischenspeicher
- A2 Zwischenspeicher
- A3 reserviert
- A4 reserviert
- A5 reserviert
- A6 reserviert - Zeiger auf aktuelle lokale Variable
- A7 reserviert - Zeiger auf CPU-Stack
-
- Zwischenspeicher können Sie in Assemblerteilen frei verwenden; diese Register
- behalten jedoch i.A. in Modula-Programmstücken nicht ihre Werte. Reservierte
- Register dürfen Sie gar nicht verändern!
-
-
- Zugriff auf globale Variable
-
- Der Zugriff auf globale Variable ist wirklich einfach: Nur den Namen benutzen
- - fertig! Der Assembler ersetzt den Namen durch die absolute Adresse der
- Variablen. Variablen sind also immer als Source- oder Destination-Adresse
- zulässig, wenn die 68000 absolute Adressierung erlaubt. Beispiele:
- 4.2 Einfache Assembleranwendungen 4 - 6
- ________________________________________________________
-
-
- CLR.W MeinCardinal
- CMPI.B #'x', MyChar
- SUBQ.L #2, LongCount
- MOVE.W j,k
- LEA ArrayVar, A0
-
- Etwas mehr müssen Sie naturgemäß tun, wenn die Variable nicht 1, 2 oder 4
- Bytes lang ist, also keine der 68000-Operandengrößen hat. Beispiele:
-
- RecTyp: RECORD
- name: ARRAY 0..9 OF CHAR;
- left, right: ADDRESS;
- END;
- VAR MyReal: LONGREAL;
- Index: CARDINAL;
- MyString: ARRAY 0..79 OF CHAR;
- MyRecord: RecTyp;
-
- BEGIN
- ASSEMBLER
- LEA MyReal, A0 ; Real-Variable auf den Parm-Stack bringen
- MOVE.L (A0)+,(A3)+
- MOVE.L (A0),(A3)+
-
- LEA MyString, A0 ; D1 := MyString Index
- MOVE.W Index, D0
- MOVE.B 0(A0,D0.W), D1
- END
- END ...
-
- Das letzte Beispiel zeigt, wie ein Stringelement erreicht werden kann. Der
- Zugriff auf beliebige Felder wird in Abschnitt 4.3 erläutert.
-
- Namen von RECORD-Feldern interpretiert der Assembler als den Abstand des
- Feldes vom RECORD-Anfang. Ein RECORD-Feld können Sie also adressieren,
- indem Sie die RECORD-Anfangsadresse in ein Register laden und den Feldnamen
- als 'Displacement' verwenden. Beachten Sie, daß trotzdem vor dem Feldnamen
- der Typname des Records stehen muß, damit der Assembler den Feldnamen
- überhaupt erkennt (es sei denn, der Assemblerteil steht innerhalb einer
- WITH-Anweisung, die das benutzte RECORD eröffnet).
-
- LEA MyRecord, A0 ; *** teste, ob MyRecord.left = NIL
- TST.L RecTyp.left (A0)
- BEQ empty
- 4.2 Einfache Assembleranwendungen 4 - 7
- ________________________________________________________
-
-
- Zugriff auf lokale Variable
-
- Lokale Variable haben keinen festen Speicherbereich, sondern werden bei
- jedem Aufruf der zugehörigen Prozedur neu angelegt (wichtig für Rekursion!).
- Auf den Beginn des Variablenbereichs, der zur gerade laufenden Prozedur
- gehört, zeigt dann das Adreßregister A6. Alle lokalen Variablen müssen relativ
- zu diesem Register adressiert werden.
-
- Der Assembler unterstützt diese Adressierung, indem er (wie bei Recordfeldern)
- lokale Variablennamen in ein Displacement umsetzt. Sie geben hinter diesem
- Displacement das Basisregister an (immer A6). Beispiele:
-
- PROCEDURE asmDemo;
- VAR j, k, MeinCardinal: CARDINAL;
- MyChar: CHAR;
- LongCount: LONGCARD;
- ArrayVar: ARRAY 0..9 OF INTEGER;
- BEGIN
- ASSEMBLER
- CLR.W MeinCardinal(A6)
- CMPI.B #'x', MyChar(A6)
- SUBQ.L #2, LongCount(A6)
- MOVE.W j(A6), k(A6)
- LEA ArrayVar(A6), A0
- ...
-
- Um lokale ARRAYs oder RECORDs zu adressieren, laden Sie deren Anfangs~
- adresse in ein Register (letztes Beispiel) und verfahren dann genauso weiter,
- wie oben für globale Variable beschrieben.
-
- Schließlich gibt es auch die (etwas kompliziertere) Möglichkeit, in lokalen
- Prozeduren Variablen der übergeordneten äußeren Prozedur zu adressieren.
- Wenn Sie auch solche Zugriffe in Assemblerroutinen benötigen sollten, sehen
- Sie sich bitte den Abschnitt 'lokale Variablen' im folgenden Teil 4.3 an.
- 4.3 Assembler für Experten 4 - 8
- ________________________________________________________
-
-
- 4.3 Assembler für Experten
-
- In diesem Abschnitt erfahren Sie, wie Sie an beliebige Modula-Daten und
- -Prozeduren von Assembler aus herankommen. Dazu müssen wir allerdings
- gelegentlich etwas 'ans Eingemachte' gehen und Details der Laufzeitorganisation
- von Modula-Programmen diskutieren. Darauf waren Sie sowieso neugierig? Um
- so besser...
-
-
- Pseudo-Opcodes
-
- Der Assembler unterstützt folgende Pseudo-Opcodes, die vor allem zum Anlegen
- von Tabellen dienen:
-
- Define Constant
- legt Konstanten als Byte, Wort oder Langwort im Code ab.
- DC.B $1001, $1002, 'a' ; legt LowBytes ab ($01,$02,$61)
- DC.W $A, $B, 12, 13
- DC.L WriteLn, WriteString ; legt Prozeduradressen ab
- DC.D 1.5, -0.3333333 ; legt Longreal-Konstanten ab
-
- Define Storage
- reserviert Platz im Code. Meist ist die Verwendung von Modula-Variablen
- einer DS-Anweisung vorzuziehen!
- DS 100 ; 100 Byte freihalten
-
- ASCII Constant
- legt Zeichenfolge im Code ab (ohne Endmarke)
- ASC "Hallo"
-
- ASCII with Zero
- legt Zeichenfolge mit Endmarke 0.B ab (Stringformat).
- ACZ "Hier folgt eine Null ->" ; so sehen auch Modula-Strings aus
-
- ASCII with Length (Pascal-like String)
- legt Zeichenfolge mit führendem Längenbyte ab.
- STR "Pascal" ; genauso wie: DC.B 6,'P','a','s','c','a','l'
-
- Synchronisieren
- erzeugt ein Null-Byte, falls der PC ungerade ist. Anzuwenden, wenn z.B.
- nach Byte-Tabellen Befehlscodes folgen.
- SYNC ; hat keine Argumente
- 4.3 Assembler für Experten 4 - 9
- ________________________________________________________
-
-
- Wenn Sie unter den Pseudo-Opcodes Anweisungen zur Definition von Labels
- auf bestimmte Adressen vermissen, liegt das nicht daran, daß wir die verges~
- sen hätten - die Deklaration von Konstanten in Modula (CONST-Deklaration)
- sowie die Deklaration von Variablen auf festen Adressen (VAR a $1234 )
- bietet ja genau diese Funktionen.
-
-
- Belegung der CPU-Register
-
- Die Register der 68000 CPU dürfen Sie in Assemblerteilen nicht alle nach
- Belieben verwenden. Der vom Compiler erzeugte Code benutzt die Register als
- Zwischenspeicher. Einige Adreßregister haben besondere Funktionen und müssen
- daher sogar langfristig ihren Inhalt behalten. Diese speziellen Adreßregister
- sind teilweise auch für den Assemblerprogrammierer von Nutzen; ihre Funktion
- wird hier genauer erläutert.
-
- D0
- .. Zwischenspeicher
- D2
-
- D3
- .. reserviert - für Register- und FOR-Variable
- D7
-
- A0 Zwischenspeicher
- A1 Zwischenspeicher
- A2 Zwischenspeicher
- A3 reserviert - Zeiger auf Parameter-Stack
- A4 reserviert - für Register-Variable und WITH
- A5 reserviert
- A6 reserviert - Zeiger auf aktuelle lokale Variable
- A7 reserviert - Zeiger auf CPU-Stack
-
- Zwischenspeicher können Sie in Assemblerteilen verwenden; diese Register
- behalten jedoch im allgemeinen in Modula-Programmstücken nicht ihre Werte.
-
- Der Zeiger auf den CPU-Stack (A7) ist Ihnen als Assembler-Programmierer
- bereits bekannt. Wie von der Architektur der 68000 vorgegeben, benutzt der
- Compiler diesen Stack zum Ablegen von Rücksprungadressen bei Unterpro~
- grammadressen; außerdem werden die lokalen Variablen für aufgerufene Proze~
- duren auf dem CPU-Stack angelegt. (Die 68000 unterstützt dies durch das
- Opcode-Paar LINK/UNLK). Auch in einigen anderen Situationen (FOR- und
- WITH-Anweisungen) wird Platz auf dem CPU-Stack benötigt. Dieser Stack
- 'wächst' bekanntlich von oben nach unten; beim Aufstapeln neuer Daten wird
- A7 erniedrigt (dekrementiert).
- 4.3 Assembler für Experten 4 - 10
- ________________________________________________________
-
-
- Der Zeiger auf den Parameter-Stack (A3) verwaltet einen weiteren Stack.
- Dieser dient speziell zur Übergabe von Parametern (und evtl. Ergebnissen) von
- Prozeduren. Auch Zwischenergebnisse bei der Auswertung von Ausdrücken
- werden dort abgelegt. Der Parameter-Stack wächst von unten nach oben, und
- zwar direkt dem CPU-Stack entgegen: Im Arbeitsspeicher eines aufgerufenen
- Programms steht A7 zunächst am oberen, A3 am unteren Rand. Dadurch wird
- der Arbeitsspeicher je nach Bedarf von beiden Stacks beansprucht.
-
- Das Register zur Verwaltung der lokalen Variablen (A5) funktioniert viel einfacher,
- als sein Name befürchten läßt. Um Rekursion zu ermöglichen, muß bei jedem
- Aufruf einer Prozedur neuer Platz für die lokalen Variablen organisiert werden.
- (Vielleicht ruft sich die Prozedur ja gerade rekursiv selbst auf; dann werden
- die vorher benutzten Variablen nach der Rückkehr wieder gebraucht!)
-
- Dazu bietet die 68000-CPU die LINK-Instruktion an: LINK A5, #Platz rettet
- A5 auf dem CPU-Stack, kopiert den Stackpointer nach A5 und subtrahiert
- <Platz> vom Stackpointer. Presto - schon zeigt der Stackpointer auf <Platz>
- Bytes freien RAM, den wir für die Variablen benutzen wollen. Am Ende der
- Prozedur genügt ein UNLK, um A5 und den Stackpointer wieder in den Zustand
- vor dem letzten LINK zu versetzen. - Das Ganze funktioniert natürlich auch
- mit anderen Registern als A5; der Compiler hat sich aber auf dieses Register
- festgelegt und hofft sehr, daß Sie es in Ihren Assemblerteilen immer schön
- heil lassen.
-
- Bleibt noch der Zeiger auf die aktuellen lokalen Variablen (A6) zu erwähnen.
- Wie Sie im vorigen Absatz gelesen haben, zeigt nach der LINK-Instruktion
- zunächst A7 auf den gerade reservierten Platz. Da sich A7 aber (etwa in
- FOR-Schleifen) verschieben wird, benötigen wir einen anderen, stabilen Zeiger
- auf die Variablen. Der Compiler reserviert hierfür A6 und erzeugt an jedem
- Prozeduranfang Code, der A6 auf die Variablen zeigen läßt.
-
-
- Zugriff auf Modula-Konstanten
-
- In Modula definierte Konstanten können Sie ohne Einschränkungen als Operan~
- den verwenden. Sowohl die Benutzung als 'Immediate'-Daten (hinter '#') als
- auch als globale Adressen oder relative Displacements ist möglich. Beispiele:
-
- MOVE.L #Konstante, D0
- LEA AdressKonst, A0
- MOVE.L D0, AdressKonst
- LEA Offset(A0), A2
- 4.3 Assembler für Experten 4 - 11
- ________________________________________________________
-
-
- Zugriff auf globale Modula-Variable, speziell ARRAYs
-
- Das Wichtigste über globale Variablen haben wir schon in Kapitel 4.2 erläutert
- (Abschnitt 'Zugriff auf globale Modula-Variable'); diese Grundlagen wollen wir
- hier nicht wiederholen. Schuldig geblieben sind wir aber die Beschreibung
- allgemeiner ARRAY-Zugriffe.
-
- Die Anfangsadresse des ARRAYs wird in ein Adreßregister geladen. Ist die
- untere Grenze des Indexbereiches nicht Null, so müssen Sie vom Indexwert
- zunächst diese Untergrenze subtrahieren - das erste Element des Feldes steht
- also immer direkt am Beginn des Feldplatzes. Der so korrigierte Index wird
- dann mit der Byte-Größe der Feldelemente multipliziert. Bei der Bestimmung
- der Elementgrößen hilft Anhang A.3. Beispiel:
-
- LEA MyArray, A0 ; MyArray Index auf den CPU-Stack bringen
- MOVE.W Index, D0
- SUB.W #LowBound, D0 ; Untergrenze vom Index abziehen
- LSL.W #3, D0 ; Index mit Elementgröße (2^3) multiplizieren
- MOVE.L 0(A0,D0.W), -(A7) ; erst die vorderen 4 Bytes auf den Stack...
- MOVE.L 4(A0,D0.W), -(A7) ; ... dann noch den Rest
-
- Ist das Produkt aus Elementlänge und Index-Untergrenze kleiner als 128, dann
- ist eine vereinfachte Version des Feldzugriffs möglich: Statt die Untergrenze
- vom Index zu subtrahieren, können Sie auch das Displacement beim
- eigentlichen Zugriff korrigieren. Beispiel für den häufigen Fall, daß die
- Untergrenze 1 ist (MyArray: ARRAY 1..n OF CARDINAL):
-
- LEA MyArray, A0 ; *** Ziel := MyArray Index
- MOVE.W Index, D0
- ADD.W D0, D0 ; Elementlänge ist 2 Byte
- MOVE.W -2(A0,D0.W), Ziel ; Zugriff mit Korrektur der Untergrenze
-
-
- Zugriff auf lokale Variable
-
- Auch zu diesem Thema finden Sie die grundlegenden Informationen in Kapitel
- 4.2. Im Folgenden werden Sie zusätzlich erfahren, wie in lokalen Prozeduren
- auf die Variablen übergeordneter Prozeduren zugegriffen wird.
-
- Auch diese Variablen sind noch nicht global, werden also zur Laufzeit dynamisch
- angelegt. Um sie wiederfinden zu können, baut der Compiler zur Laufzeit eine
- Zeigerkette auf: In lokalen Prozeduren zeigt Adreßregister A6 gar nicht direkt
- auf die aktuellen Variablen der laufenden Prozedur (da haben wir oben ein
- bißchen vereinfacht). Unter der Adresse (A6) finden Sie zunächst einen Zeiger
- auf die Variablen der übergeordneten Prozedur; erst dahinter (Adresse 4(A6))
- beginnen die eigenen Variablen. Bei der Adressierung über den Variablennamen
- wird dieser Offset natürlich automatisch berücksichtigt.
- 4.3 Assembler für Experten 4 - 12
- ________________________________________________________
-
-
- Damit ist Ihnen sicher schon klar, wie Sie an die äußeren Daten herankommen.
- Das folgenden Beispiel beseitigt hoffentlich die letzten Zweifel:
-
- PROCEDURE außen;
- VAR aVar: CARDINAL;
-
- PROCEDURE innen;
- VAR iVar: CARDINAL;
- BEGIN
- ASSEMBLER
- MOVE.L (A6), A0 ; A0 zeigt auf Variablen von 'außen'
- ADDQ #1, aVar(A0) ; jetzt ist aVar erreichbar
- MOVE aVar(A0), iVar(A6)
- END
- END innen;
- ..
- END außen;
-
- Angenommen, in diesem Beispiel ist auch 'außen' lokal zu einer dritten Prozedur
- 'ganzAußen' deklariert, und an deren Variablen wollen Sie nun von 'innen'
- herankommen - dann brauchen Sie die Zeigerkette natürlich nur eine Stufe
- weiter zu verfolgen, denn auch 'außen' hat vor seinen Variablen einen Zeiger
- auf die übergeordneten Daten. Also:
-
- MOVE.L (A6), A0 ; A0 zeigt auf Variablen von 'außen'
- MOVE.L (A0), A0 ; A0 zeigt auf Variablen von 'ganzAußen'
-
- Da wir vorhaben, einige Optimierungen an der Code-Erzeugung des Compilers
- vorzunehmen, weisen wir vorsorglich darauf hin, daß Zugriffe auf lokale
- Variablen innerhalb von WITH-Anweisungen nicht durchgeführt werden sollen.
- Statt dessen ist besser eine lokale Prozedur anzulegen, die innerhalb der
- WITH-Anweisung aufgerufen werden kann und in der dann die Assembler-
- Zugriffe, wie oben beschrieben, auf die lokalen Variablen erfolgen können.
- 4.3 Assembler für Experten 4 - 13
- ________________________________________________________
-
-
- Fehlerprüfungen vom Assembler
-
- Der Assembler führt ein paar Überwachungen durch, auf die wir Sie vielleicht
- hinweisen sollten:
-
- Führen Sie einen Datenzugriff auf eine Modula-Variable durch (z.B. MOVE,
- ADD, nicht jedoch LEA), prüft der Compiler, je nachdem, ob dies sinnvoll sein
- kann. So macht es keinen Sinn, MOVE var,D0 zu programmieren, wenn die
- Variable lokal ist. Ebenso unsinnig wäre der Zugriff mit einem Adreß-
- Displacement bei einer globalen Variablen. In solchen Fällen meldet der
- Assembler den Fehler Logisch falsche Adressierung. Die selbe Fehlermeldung
- können Sie auch bei anderen, sinnlosen Verwendungen von Modula-Bezeichnern
- erwarten. Allerdings meckert er nicht, wenn Sie beim Zugriff auf eine lokale
- Variable das falsche Adreßregister benutzen (also nicht A6), Sie könnten A6 ja
- in das verwendete Register kopiert haben.
-
-
- Aufruf von globalen Modula-Prozeduren
-
- Um eine global unter Modula deklarierte Prozedur aufzurufen, genügt ein JSR
- ProzedurName. Der Compiler erzeugt daraus einen Aufruf mit absoluter
- Adressierung (Sie erinnern sich an die Grundregel aus Abschnitt 4.1?). Das
- funktioniert ohne weiteres auch für importierte Prozeduren, z.B. JSR WriteLn.
-
- Wenn eine Prozedur Parameter erwartet, liegt es beim Aufruf aus Assembler~
- programmen in Ihrer Verantwortung, diese richtig bereitzustellen. Das geht so:
-
- * Vor dem Aufruf einer Prozedur werden alle Parameter in der Reihenfolge
- ihrer Deklaration im Prozedurkopf auf den Parameter-Stack gebracht.
-
- * Bei Wert-Parametern ('call by value') wird eine Kopie des Wertes übergeben;
- bei VAR-Parametern ('call by reference') übergeben Sie die Adresse der
- Variablen.
-
- * Wichtig bei Wert-Übergaben: Der Parameter-Stackpointer muß nach jedem
- Parameter synchronisiert werden! Ist ein Parameter eine ungerade Anzahl
- Bytes lang, anschließend A3 um 1 Byte erhöhen!
-
- * An Open ARRAY-Parameter wird (bei Wert- und bei VAR-Parametern)
- immer die Adresse und anschließend der HIGH-Wert (als Wort) übergeben. Bei
- Übergabe an Open ARRAY Wert-Parameter muß eine Kopie des Parameters
- erzeugt und übergeben werden, damit die aufgerufene Prozedur das Original
- nicht verändern kann.
- 4.3 Assembler für Experten 4 - 14
- ________________________________________________________
-
-
- * Die aufgerufene Prozedur sorgt für das Abräumen der Parameter vom A3-Stack.
- Funktions-Prozeduren legen vor der Rückkehr statt dessen ihren Ergebniswert
- dort ab.
-
- Noch nicht alles klar? Dann helfen sicher einige Beispiele:
-
- MODULE AsmBsp;
-
- FROM SYSTEM IMPORT ASSEMBLER;
-
- VAR MeinCardinal : CARDINAL; (* 16 Bit: 1 Word *)
- MeinString : ARRAY 0..39 OF CHAR;
- MeinReal, DeinReal : LONGREAL;
-
- BEGIN
- ASSEMBLER
- ; WriteCard (c: LONGCARD; space: CARDINAL)
- MOVEQ.L #0, D0 ; Long-Wert in D0 löschen
- MOVE.W MeinCardinal, D0 ;Wert von MeinCardinal
- MOVE.L D0, (A3)+ ; und auf den Stack damit
- MOVE.W #8, (A3)+ ;Feldbreite für Ausgabe
- JSR WriteCard ;Ausgeben
-
- ; ReadCard (VAR c: CARDINAL)
- MOVE.L #MeinCardinal, (A3)+ ; Adresse von MeinCardinal
- JSR ReadCard ;Einlesen
-
- ; ReadString (VAR s: ARRAY OF CHAR)
- LEA MeinString, A0 ;ein ARRAY 0..39 OF CHAR
- MOVE.L A0, (A3)+ ;Adresse des Strings
- MOVE.W #SIZE(MeinString)-1,(A3)+ ;HIGH-Wert
- JSR ReadString
-
- ; sin (x: REAL): REAL
- LEA MeinReal, A0 ; Realzahl auf den Stack
- MOVE.L (A0)+, (A3)+ ; ..
- MOVE.L (A0), (A3)+ ; ..
- JSR sin ; sin (MeinReal) berechnen
- LEA DeinReal, A0 ; Real-Ergebnis vom Stack abholen
- MOVE.L -(A3), 4(A0) ; ..
- MOVE.L -(A3), (A0) ; ..
- 4.3 Assembler für Experten 4 - 15
- ________________________________________________________
-
-
- Aufruf von lokalen Modula-Prozeduren
-
- Keine Sorge - alles, was Sie eben über Parameterübergaben gelernt haben, gilt
- für lokale Prozeduren ganz genau wie für globale. Einen Unterschied gibt es
- nur beim Aufruf selbst: Lokale Prozeduren werden PC-relativ adressiert, also
- durch BSR ProzedurName.
-
- Vor dem Aufruf müssen Sie allerdings noch eine weitere Information bereitstel~
- len: einen Zeiger auf den nächsthöheren erreichbaren Variablenbereich, der im
- Datenregister D2 zu übergeben ist. Welcher Bereich sichtbar ist, hängt von
- der Deklarations-Hierarchie von Aufrufer und aufgerufener Prozedur ab. Das
- folgende Beispiel illustriert die beiden gängigen Anordnungen und zeigt die
- Bereitstellung des Zeigers:
-
- PROCEDURE außen;
-
- PROCEDURE innen1;
- END innen1;
-
- PROCEDURE innen2;
-
- PROCEDURE ganzInnen;
- END ganzInnen;
-
- BEGIN (* innen2 *)
- ..
- MOVE.L A6, D2 ; Aufrufer ruft zu ihm lokale Prozedur:
- BSR ganzInnen ; Variablen des Aufrufers sichtbar
- ..
- MOVE.L (A6), D2 ; Aufrufer ruft Prozedur auf gleicher Ebene:
- BSR innen1 ; die Variablen, die der Aufrufer sieht,
- ..
- ; sieht auch der Gerufene
- MOVE.L (A6), D2 ; rekursive Aufrufe rufen ebenfalls
- BSR innen2 ; eine Prozedur auf gleicher Ebene
- ..
- END innen2;
-
- BEGIN
- ..
- END außen;
-
-
- Nochmal in Kurzfassung: Parameter bereitstellen (wie unter "globale
- Prozeduren" beschrieben); je nach Aufruf-Hierarchie passenden Zeiger nach
- D2 holen; Aufruf mit BSR.
- 4.3 Assembler für Experten 4 - 16
- ________________________________________________________
-
-
- Übernahme von Parametern (Link-Option)
-
- Normalerweise sorgt der Compiler dafür, daß zu Beginn einer Prozedur die
- Parameter in lokale Variablen übernommen werden. In Assembleranweisungen
- können Sie - wie auch unter Modula - die Parameter genau wie lokale Variablen
- über ihre Namen adressieren.
-
- Wenn Sie einen kompletten Prozedurrumpf in Assembler implementieren wollen,
- kann das Umkopieren vom Parameter-Stack in lokale Variablen (und von dort
- später in CPU-Register) ineffizient sein. Oft ist es wünschenswert, die Parame~
- ter direkt vom Stack in CPU-Register zu übernehmen. In diesem Fall können
- Sie das Umkopieren durch den Compiler unterdrücken, indem Sie vor den
- Prozedurrumpf die Compileroption (*$ L- *) setzen (s. 3.3). Dann gilt:
-
- * Die Parameter stehen so auf dem Stack (A3), wie oben im Abschnitt 'Aufruf
- globaler Modula-Prozeduren' beschrieben wird. Die aufgerufene Prozedur (also
- Ihr Assemblerprogramm) ist für das Abräumen der Parameter vom Stack
- verantwortlich.
-
- * Funktions-Prozeduren müssen vor der Rückkehr den Ergebnis-Wert auf den
- Parameterstack bringen.
-
- * Prozeduren, die unter (*$ L- *) übersetzt werden, dürfen keine lokalen
- Variablen haben! Falls Sie außer den CPU-Registern zusätzlichen Speicherplatz
- benötigen, sollten Sie ihn auf dem CPU-Stack (A7) oder mittels DS-Anweisungen
- selbst anlegen.
-
- Als Beispiel hier noch einmal der 'Bit Reversal'-Algorithmus aus 4.1, diesmal als
- komplette Funktion, wie sie in einem FFT-Modul (Fast Fourier Transformation)
- Verwendung finden könnte:
-
- PROCEDURE BitRev (c: CARDINAL): CARDINAL;
- (*$ L- *)
- BEGIN
- ASSEMBLER
- MOVE -(A3),D0 ; hole Parameter
- MOVEQ #15, D1 ; 16 Bit sind umzukehren
- lp LSR #1, D0 ; schiebe Bit ins eXtend-Flag
- ROXL #1, D2 ; .. und von dort ins Zielregister
- DBF D1, lp ; das Ganze 16 mal
- MOVE D2,(A3)+ ; schreibe Ergebnis auf den Stack
- END;
- END BitRev;
- (*$ L= *)
-